package cn.iocoder.yudao.module.wallet.service.eth;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.i18n.utils.MessageUtils;
import cn.iocoder.yudao.module.wallet.controller.app.request.TransferEthRequest;
import cn.iocoder.yudao.module.wallet.controller.app.response.TransactionResponse;
import cn.iocoder.yudao.module.wallet.entity.WalletEntity;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.bitcoinj.crypto.ChildNumber;
import org.bitcoinj.crypto.DeterministicKey;
import org.bitcoinj.crypto.HDKeyDerivation;
import org.bitcoinj.wallet.DeterministicSeed;
import org.springframework.stereotype.Service;
import org.web3j.crypto.*;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.DefaultBlockParameterName;
import org.web3j.protocol.core.methods.response.*;
import org.web3j.tx.ChainId;
import org.web3j.utils.Convert;
import org.web3j.utils.Numeric;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;

/**
 * @author liutao
 * @version 1.0
 * @description: TODO
 * @date 2024/10/31 16:27
 */
@Slf4j
@Service
public class EthServiceImpl implements EthService {

    @Resource
    private Web3j web3j;

    public WalletEntity importMnemonic(String mnemonic, String password) {
        String ETH_TYPE = "m/44'/60'/0'/0/0";
        Map<String, String> resultMap = new LinkedHashMap<>();
        List<String> list = StrUtil.split(mnemonic, " ");
        String passphrase = "";
        long creationTimeSeconds = System.currentTimeMillis() / 1000;
        DeterministicSeed ds = new DeterministicSeed(list, null, passphrase, creationTimeSeconds);
        String[] pathArray = ETH_TYPE.split("/");
        //根私钥
        byte[] seedBytes = ds.getSeedBytes();
        DeterministicKey dkKey = HDKeyDerivation.createMasterPrivateKey(seedBytes);
        for (int i = 1; i < pathArray.length; i++) {
            ChildNumber childNumber;
            if (pathArray[i].endsWith("'")) {
                int number = Integer.parseInt(pathArray[i].substring(0, pathArray[i].length() - 1));
                childNumber = new ChildNumber(number, true);
            } else {
                int number = Integer.parseInt(pathArray[i]);
                childNumber = new ChildNumber(number, false);
            }
            dkKey = HDKeyDerivation.deriveChildKey(dkKey, childNumber);
        }
        
        ECKeyPair keyPair = ECKeyPair.create(dkKey.getPrivKeyBytes());
        WalletFile walletFile;
        try {
            walletFile = Wallet.createLight(password, keyPair);
        } catch (CipherException e) {
            log.error("以太坊生成失败，错误:{}", e.getMessage());
            throw new ServiceException(500, "以太坊生成失败");
        }
        String privateKey = keyPair.getPrivateKey().toString(16);
        String publicKey = keyPair.getPublicKey().toString(16);
        String address = "0x" + walletFile.getAddress();
        log.info("ETH生成成功 地址：{}, 私钥：{}, 公钥：{}", address, privateKey, publicKey);
        WalletEntity walletEntity = WalletEntity.builder()
                .address(address)
                .publicKey(publicKey)
                .privateKey(privateKey)
                .build();
        return walletEntity;
    }

    /**
     * 获取最新的区块号
     * @return
     */
    @Override
    public BigInteger getBlockNumber() {
        try {
            EthBlockNumber ethBlockNumber = web3j.ethBlockNumber().sendAsync().get();
            BigInteger blockNumber = ethBlockNumber.getBlockNumber();
            log.info("BlockNumber: {}", blockNumber);
            return blockNumber;
        } catch (InterruptedException | ExecutionException e) {
            log.info("获取最新的区块号失败,错误：{}", e.getMessage());
            throw new ServiceException(500, "获取最新的区块号失败");
//            throw new ServiceException(500, MessageUtils.get("get.BlockNumber.error"));
        }
    }
    /**
     * 获取最新的区块号
     * @return
     */
    @Override
    public String getWebBlockNumber() {
        BigInteger blockNumber = getBlockNumber();
        return "0x"+blockNumber.toString(16);
    }

    /**
     * 获取账户的Nonce
     *
     * @param address
     * @return
     */
    @Override
    public BigInteger getNonce(String address) {
        try {
            EthGetTransactionCount getNonce = web3j.ethGetTransactionCount(address, DefaultBlockParameterName.PENDING).send();
            if (getNonce == null) {
                throw new ServiceException("net error");
            }
            return getNonce.getTransactionCount();
        } catch (IOException e) {
            log.error("获取账户的Nonce失败：{}", e.getMessage());
            throw new ServiceException("获取账户的Nonce失败");
        }
    }

    /**
     * @description 获取链Id
     * @author newonexd
     * @date 2022/6/22 21:12
     * <p>
     * * @return BigInteger
     */
    @Override
    public BigInteger doGetChainId() {
        try {
            EthChainId ethChainId = web3j.ethChainId().sendAsync().get();
            BigInteger chainId = ethChainId.getChainId();
            log.info("Ethereum Chain Id: {}", chainId);
            return chainId;
        } catch (InterruptedException | ExecutionException e) {
            throw new ServiceException("获取链Id错误");
        }
    }

    @Override
    public String transfer(TransferEthRequest req) {
        String fromAddr = req.getFromAddr();
        String privateKey = req.getPrivateKey();
        String toAddr = req.getToAddr();
        BigDecimal amount = req.getAmount();
        String data = req.getData();
        if (!WalletUtils.isValidAddress(toAddr)) {
            log.info("转账目的地址不合法");
            return "";
        }

        // 获得nonce
        BigInteger nonce = getNonce(fromAddr);
        // value 转换
        BigInteger value = Convert.toWei(amount, Convert.Unit.ETHER).toBigInteger();
        BigInteger gasPrice = doGetEthGasPrice();
        // 构建交易
        org.web3j.protocol.core.methods.request.Transaction transaction = org.web3j.protocol.core.methods.request.Transaction.createEtherTransaction(fromAddr, nonce, gasPrice, null, toAddr, value);
        // 计算gasLimit
        BigInteger gasLimit = getTransactionGasLimit(transaction);

        // 查询调用者余额，检测余额是否充足
        BigDecimal ethBalance = getBalance(fromAddr);
        BigDecimal balance = Convert.toWei(ethBalance, Convert.Unit.ETHER);
        // balance < amount + gasLimit ??
        if (balance.compareTo(amount.add(new BigDecimal(gasLimit.toString()))) < 0) {
            throw new ServiceException("余额不足，请核实");
        }
        BigInteger chainId = doGetChainId();
        return signAndSend(nonce, gasPrice, gasLimit, toAddr, value, data, chainId.longValue(), privateKey);
    }

    @Override
    public TransactionResponse transaction(String hash) {
        try {
            EthTransaction transaction = web3j.ethGetTransactionByHash(hash).send();
            EthGasPrice gasPrice = web3j.ethGasPrice().send();
            return TransactionResponse.builder()
                    .gasPrice(gasPrice.getGasPrice())
                    .ethTransaction(transaction)
                    .build();
        } catch (Exception e) {
            throw new ServiceException("获取交易信息失败");
        }
    }

    public String signAndSend(BigInteger nonce, BigInteger gasPrice, BigInteger gasLimit, String to, BigInteger value, String data, long chainId, String privateKey) {
        String txHash = "";
        RawTransaction rawTransaction = RawTransaction.createTransaction(nonce, gasPrice, gasLimit, to, value, data);
        if (privateKey.startsWith("0x")) {
            privateKey = privateKey.substring(2);
        }

        ECKeyPair ecKeyPair = ECKeyPair.create(new BigInteger(privateKey, 16));
        Credentials credentials = Credentials.create(ecKeyPair);
        if (!WalletUtils.isValidAddress(credentials.getAddress())) {
            log.info("转账账户不合法");
            return "";
        }

        byte[] signMessage;
        if (chainId > ChainId.NONE) {
            signMessage = TransactionEncoder.signMessage(rawTransaction, chainId, credentials);
        } else {
            signMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
        }

        String signData = Numeric.toHexString(signMessage);
        if (!signData.isEmpty()) {
            try {
                EthSendTransaction send = web3j.ethSendRawTransaction(signData).send();
                txHash = send.getTransactionHash();
                log.info(JSONUtil.toJsonStr(send));
            } catch (IOException e) {
                throw new ServiceException("交易异常");
            }
        }
        return txHash;
    }

    /**
     * @description 获取Gas价格
     * @author newonexd
     * @date 2022/6/22 21:11
     * <p>
     * * @return BigInteger
     */
    @Override
    public BigInteger doGetEthGasPrice() {
        try {
            EthGasPrice ethGasPrice = web3j.ethGasPrice().sendAsync().get();
            BigInteger gasPrice = ethGasPrice.getGasPrice();
            log.info("Ethereum Gas Price: {}", gasPrice);
            return gasPrice;
        } catch (InterruptedException | ExecutionException e) {
            throw new ServiceException("获取Gas价格失败");
        }
    }

    /**
     * 获取ETH余额
     *
     * @param address
     * @return
     */
    @Override
    public BigDecimal getBalance(String address) {
        try {
            EthGetBalance ethGetBalance = web3j.ethGetBalance(address, DefaultBlockParameterName.LATEST).send();
            return Convert.fromWei(new BigDecimal(ethGetBalance.getBalance()), Convert.Unit.ETHER);
        } catch (IOException e) {
            log.error("获取ETH余额失败：{}", e.getMessage());
            throw new ServiceException("获取ETH余额失败");
        }
    }

    /**
     * 估算手续费上限
     *
     * @param transaction
     * @return
     */
    public BigInteger getTransactionGasLimit(org.web3j.protocol.core.methods.request.Transaction transaction) {
        try {
            EthEstimateGas ethEstimateGas = web3j.ethEstimateGas(transaction).send();
            if (ethEstimateGas.hasError()) {
                throw new ServiceException(ethEstimateGas.getError().getMessage());
            }
            return ethEstimateGas.getAmountUsed();
        } catch (IOException e) {
            throw new ServiceException("net error");
        }
    }

}
